"""
缓存服务实现

提供Redis缓存层和缓存策略优化，包括：
- Redis缓存管理
- 缓存策略优化算法
- 缓存命中率监控
- 智能缓存预热和清理
"""
import json
import pickle
import hashlib
import asyncio
from typing import Any, Dict, List, Optional, Union, Callable
from datetime import datetime, timedelta
from dataclasses import dataclass, field
from collections import defaultdict, deque
import time

try:
    import redis
    import redis.asyncio as aioredis
    REDIS_AVAILABLE = True
except ImportError:
    REDIS_AVAILABLE = False
    print("Redis not available, using in-memory cache")


@dataclass
class CacheEntry:
    """缓存条目"""
    key: str
    value: Any
    created_at: datetime
    expires_at: Optional[datetime] = None
    access_count: int = 0
    last_accessed: datetime = field(default_factory=datetime.now)
    size_bytes: int = 0
    tags: List[str] = field(default_factory=list)


@dataclass
class CacheStats:
    """缓存统计"""
    total_requests: int = 0
    cache_hits: int = 0
    cache_misses: int = 0
    evictions: int = 0
    memory_usage_bytes: int = 0
    average_response_time_ms: float = 0.0
    hit_rate: float = 0.0


class InMemoryCache:
    """内存缓存实现（Redis不可用时的后备方案）"""
    
    def __init__(self, max_size: int = 1000):
        self.cache: Dict[str, CacheEntry] = {}
        self.max_size = max_size
        self.access_order = deque()
        
    async def get(self, key: str) -> Optional[Any]:
        """获取缓存值"""
        if key in self.cache:
            entry = self.cache[key]
            
            # 检查是否过期
            if entry.expires_at and datetime.now() > entry.expires_at:
                await self.delete(key)
                return None
            
            # 更新访问信息
            entry.access_count += 1
            entry.last_accessed = datetime.now()
            
            # 更新访问顺序（LRU）
            if key in self.access_order:
                self.access_order.remove(key)
            self.access_order.append(key)
            
            return entry.value
        
        return None
    
    async def set(self, key: str, value: Any, expire_seconds: Optional[int] = None) -> bool:
        """设置缓存值"""
        try:
            # 序列化值以计算大小
            serialized_value = pickle.dumps(value)
            size_bytes = len(serialized_value)
            
            # 计算过期时间
            expires_at = None
            if expire_seconds:
                expires_at = datetime.now() + timedelta(seconds=expire_seconds)
            
            # 创建缓存条目
            entry = CacheEntry(
                key=key,
                value=value,
                created_at=datetime.now(),
                expires_at=expires_at,
                size_bytes=size_bytes
            )
            
            # 检查是否需要清理空间
            if len(self.cache) >= self.max_size and key not in self.cache:
                await self._evict_lru()
            
            self.cache[key] = entry
            
            # 更新访问顺序
            if key in self.access_order:
                self.access_order.remove(key)
            self.access_order.append(key)
            
            return True
            
        except Exception as e:
            print(f"设置缓存失败: {e}")
            return False
    
    async def delete(self, key: str) -> bool:
        """删除缓存值"""
        if key in self.cache:
            del self.cache[key]
            if key in self.access_order:
                self.access_order.remove(key)
            return True
        return False
    
    async def exists(self, key: str) -> bool:
        """检查键是否存在"""
        return key in self.cache
    
    async def clear(self) -> bool:
        """清空缓存"""
        self.cache.clear()
        self.access_order.clear()
        return True
    
    async def _evict_lru(self):
        """清理最近最少使用的条目"""
        if self.access_order:
            lru_key = self.access_order.popleft()
            if lru_key in self.cache:
                del self.cache[lru_key]


class CacheService:
    """缓存服务"""
    
    def __init__(self, redis_url: str = "redis://localhost:6379", 
                 fallback_to_memory: bool = True):
        self.redis_url = redis_url
        self.fallback_to_memory = fallback_to_memory
        
        # 缓存实例
        self.redis_client: Optional[aioredis.Redis] = None
        self.memory_cache: Optional[InMemoryCache] = None
        self.use_redis = False
        
        # 缓存统计
        self.stats = CacheStats()
        self.performance_history = deque(maxlen=1000)
        
        # 缓存策略配置
        self.cache_config = {
            'default_ttl': 3600,  # 1小时
            'max_memory_cache_size': 1000,
            'compression_threshold': 1024,  # 1KB
            'batch_size': 100,
            'cleanup_interval': 300,  # 5分钟
            'preload_popular_keys': True,
            'adaptive_ttl': True
        }
        
        # 缓存键模式和TTL映射
        self.key_patterns = {
            'literature:*': 7200,      # 文献搜索结果 - 2小时
            'experiment:*': 3600,      # 实验设计 - 1小时
            'dataset:*': 86400,        # 数据集信息 - 24小时
            'analysis:*': 1800,        # 分析结果 - 30分钟
            'user_session:*': 1800,    # 用户会话 - 30分钟
            'agent_state:*': 600,      # Agent状态 - 10分钟
            'communication:*': 300     # 通信消息 - 5分钟
        }
        
        # 热点键跟踪
        self.hot_keys = defaultdict(int)
        self.key_access_patterns = defaultdict(list)
    
    async def initialize(self):
        """初始化缓存服务"""
        if REDIS_AVAILABLE:
            try:
                # 尝试连接Redis
                self.redis_client = aioredis.from_url(
                    self.redis_url,
                    encoding="utf-8",
                    decode_responses=False,
                    socket_connect_timeout=5,
                    socket_timeout=5
                )
                
                # 测试连接
                await self.redis_client.ping()
                self.use_redis = True
                print("✅ Redis缓存已连接")
                
            except Exception as e:
                print(f"⚠️ Redis连接失败: {e}")
                if self.fallback_to_memory:
                    self._setup_memory_cache()
                else:
                    raise
        else:
            if self.fallback_to_memory:
                self._setup_memory_cache()
            else:
                raise RuntimeError("Redis不可用且未启用内存缓存后备")
    
    def _setup_memory_cache(self):
        """设置内存缓存"""
        self.memory_cache = InMemoryCache(
            max_size=self.cache_config['max_memory_cache_size']
        )
        self.use_redis = False
        print("📝 使用内存缓存")
    
    async def get(self, key: str, default: Any = None) -> Any:
        """获取缓存值"""
        start_time = time.time()
        
        try:
            self.stats.total_requests += 1
            
            # 记录键访问模式
            self.hot_keys[key] += 1
            self.key_access_patterns[key].append(datetime.now())
            
            # 从缓存获取值
            if self.use_redis and self.redis_client:
                value = await self._get_from_redis(key)
            elif self.memory_cache:
                value = await self.memory_cache.get(key)
            else:
                value = None
            
            # 更新统计
            if value is not None:
                self.stats.cache_hits += 1
            else:
                self.stats.cache_misses += 1
                value = default
            
            # 更新命中率
            self.stats.hit_rate = self.stats.cache_hits / self.stats.total_requests
            
            # 记录响应时间
            response_time = (time.time() - start_time) * 1000
            self.stats.average_response_time_ms = (
                self.stats.average_response_time_ms * 0.9 + response_time * 0.1
            )
            
            # 记录性能历史
            self.performance_history.append({
                'timestamp': datetime.now(),
                'key': key,
                'hit': value is not None and value != default,
                'response_time_ms': response_time
            })
            
            return value
            
        except Exception as e:
            print(f"获取缓存失败 {key}: {e}")
            self.stats.cache_misses += 1
            return default
    
    async def set(self, key: str, value: Any, expire_seconds: Optional[int] = None) -> bool:
        """设置缓存值"""
        try:
            # 确定TTL
            if expire_seconds is None:
                expire_seconds = self._get_adaptive_ttl(key)
            
            # 设置缓存
            if self.use_redis and self.redis_client:
                success = await self._set_to_redis(key, value, expire_seconds)
            elif self.memory_cache:
                success = await self.memory_cache.set(key, value, expire_seconds)
            else:
                success = False
            
            return success
            
        except Exception as e:
            print(f"设置缓存失败 {key}: {e}")
            return False
    
    async def delete(self, key: str) -> bool:
        """删除缓存值"""
        try:
            if self.use_redis and self.redis_client:
                result = await self.redis_client.delete(key)
                return result > 0
            elif self.memory_cache:
                return await self.memory_cache.delete(key)
            else:
                return False
                
        except Exception as e:
            print(f"删除缓存失败 {key}: {e}")
            return False
    
    async def exists(self, key: str) -> bool:
        """检查键是否存在"""
        try:
            if self.use_redis and self.redis_client:
                return await self.redis_client.exists(key) > 0
            elif self.memory_cache:
                return await self.memory_cache.exists(key)
            else:
                return False
                
        except Exception as e:
            print(f"检查缓存存在性失败 {key}: {e}")
            return False
    
    async def clear_pattern(self, pattern: str) -> int:
        """清除匹配模式的键"""
        try:
            if self.use_redis and self.redis_client:
                keys = await self.redis_client.keys(pattern)
                if keys:
                    return await self.redis_client.delete(*keys)
                return 0
            elif self.memory_cache:
                # 内存缓存的模式匹配清理
                import fnmatch
                keys_to_delete = [
                    key for key in self.memory_cache.cache.keys()
                    if fnmatch.fnmatch(key, pattern)
                ]
                for key in keys_to_delete:
                    await self.memory_cache.delete(key)
                return len(keys_to_delete)
            else:
                return 0
                
        except Exception as e:
            print(f"清除模式缓存失败 {pattern}: {e}")
            return 0
    
    async def _get_from_redis(self, key: str) -> Any:
        """从Redis获取值"""
        try:
            data = await self.redis_client.get(key)
            if data is not None:
                return pickle.loads(data)
            return None
        except Exception as e:
            print(f"从Redis获取失败 {key}: {e}")
            return None
    
    async def _set_to_redis(self, key: str, value: Any, expire_seconds: int) -> bool:
        """设置值到Redis"""
        try:
            # 序列化值
            serialized_value = pickle.dumps(value)
            
            # 检查是否需要压缩
            if len(serialized_value) > self.cache_config['compression_threshold']:
                import gzip
                serialized_value = gzip.compress(serialized_value)
                key = f"compressed:{key}"
            
            # 设置到Redis
            await self.redis_client.setex(key, expire_seconds, serialized_value)
            return True
            
        except Exception as e:
            print(f"设置到Redis失败 {key}: {e}")
            return False
    
    def _get_adaptive_ttl(self, key: str) -> int:
        """获取自适应TTL"""
        if not self.cache_config['adaptive_ttl']:
            return self.cache_config['default_ttl']
        
        # 根据键模式确定TTL
        for pattern, ttl in self.key_patterns.items():
            import fnmatch
            if fnmatch.fnmatch(key, pattern):
                # 根据访问频率调整TTL
                access_count = self.hot_keys.get(key, 0)
                if access_count > 10:  # 热点键
                    return int(ttl * 1.5)  # 延长TTL
                elif access_count < 2:  # 冷键
                    return int(ttl * 0.5)  # 缩短TTL
                return ttl
        
        return self.cache_config['default_ttl']
    
    async def batch_get(self, keys: List[str]) -> Dict[str, Any]:
        """批量获取缓存值"""
        results = {}
        
        if self.use_redis and self.redis_client:
            try:
                # Redis批量获取
                values = await self.redis_client.mget(keys)
                for key, value in zip(keys, values):
                    if value is not None:
                        try:
                            results[key] = pickle.loads(value)
                        except:
                            results[key] = None
                    else:
                        results[key] = None
            except Exception as e:
                print(f"批量获取失败: {e}")
                # 回退到单个获取
                for key in keys:
                    results[key] = await self.get(key)
        else:
            # 内存缓存批量获取
            for key in keys:
                results[key] = await self.get(key)
        
        return results
    
    async def batch_set(self, items: Dict[str, Any], expire_seconds: Optional[int] = None) -> bool:
        """批量设置缓存值"""
        try:
            if self.use_redis and self.redis_client:
                # Redis批量设置
                pipe = self.redis_client.pipeline()
                for key, value in items.items():
                    serialized_value = pickle.dumps(value)
                    ttl = expire_seconds or self._get_adaptive_ttl(key)
                    pipe.setex(key, ttl, serialized_value)
                await pipe.execute()
                return True
            else:
                # 内存缓存批量设置
                for key, value in items.items():
                    await self.set(key, value, expire_seconds)
                return True
                
        except Exception as e:
            print(f"批量设置失败: {e}")
            return False
    
    async def preload_cache(self, preload_func: Callable[[str], Any], 
                          keys: List[str]) -> int:
        """预加载缓存"""
        loaded_count = 0
        
        try:
            for key in keys:
                if not await self.exists(key):
                    try:
                        value = await preload_func(key)
                        if value is not None:
                            await self.set(key, value)
                            loaded_count += 1
                    except Exception as e:
                        print(f"预加载键失败 {key}: {e}")
            
            print(f"预加载完成: {loaded_count}/{len(keys)} 个键")
            return loaded_count
            
        except Exception as e:
            print(f"预加载过程失败: {e}")
            return loaded_count
    
    async def cleanup_expired(self) -> int:
        """清理过期缓存"""
        cleaned_count = 0
        
        try:
            if self.memory_cache:
                # 内存缓存清理过期项
                current_time = datetime.now()
                expired_keys = []
                
                for key, entry in self.memory_cache.cache.items():
                    if entry.expires_at and current_time > entry.expires_at:
                        expired_keys.append(key)
                
                for key in expired_keys:
                    await self.memory_cache.delete(key)
                    cleaned_count += 1
            
            # Redis会自动清理过期键，这里主要清理访问模式数据
            cutoff_time = datetime.now() - timedelta(hours=24)
            for key in list(self.key_access_patterns.keys()):
                # 清理24小时前的访问记录
                self.key_access_patterns[key] = [
                    access_time for access_time in self.key_access_patterns[key]
                    if access_time > cutoff_time
                ]
                
                # 如果没有最近访问记录，删除键
                if not self.key_access_patterns[key]:
                    del self.key_access_patterns[key]
                    if key in self.hot_keys:
                        del self.hot_keys[key]
            
            self.stats.evictions += cleaned_count
            return cleaned_count
            
        except Exception as e:
            print(f"清理过期缓存失败: {e}")
            return cleaned_count
    
    async def get_cache_info(self) -> Dict[str, Any]:
        """获取缓存信息"""
        info = {
            'cache_type': 'redis' if self.use_redis else 'memory',
            'stats': {
                'total_requests': self.stats.total_requests,
                'cache_hits': self.stats.cache_hits,
                'cache_misses': self.stats.cache_misses,
                'hit_rate': self.stats.hit_rate,
                'evictions': self.stats.evictions,
                'average_response_time_ms': self.stats.average_response_time_ms
            },
            'hot_keys': dict(sorted(self.hot_keys.items(), 
                                  key=lambda x: x[1], reverse=True)[:10]),
            'config': self.cache_config
        }
        
        if self.use_redis and self.redis_client:
            try:
                redis_info = await self.redis_client.info('memory')
                info['redis_memory'] = {
                    'used_memory': redis_info.get('used_memory', 0),
                    'used_memory_human': redis_info.get('used_memory_human', '0B'),
                    'maxmemory': redis_info.get('maxmemory', 0)
                }
            except:
                pass
        elif self.memory_cache:
            info['memory_cache'] = {
                'entries_count': len(self.memory_cache.cache),
                'max_size': self.memory_cache.max_size
            }
        
        return info
    
    async def optimize_cache_strategy(self) -> Dict[str, Any]:
        """优化缓存策略"""
        optimization_results = {
            'actions_taken': [],
            'performance_improvement': 0.0,
            'recommendations': []
        }
        
        try:
            # 分析访问模式
            access_analysis = self._analyze_access_patterns()
            
            # 优化TTL设置
            ttl_optimizations = await self._optimize_ttl_settings(access_analysis)
            optimization_results['actions_taken'].extend(ttl_optimizations)
            
            # 识别热点键
            hot_key_optimizations = self._optimize_hot_keys()
            optimization_results['actions_taken'].extend(hot_key_optimizations)
            
            # 清理冷键
            cold_key_cleanup = await self._cleanup_cold_keys()
            optimization_results['actions_taken'].append(
                f"清理了 {cold_key_cleanup} 个冷键"
            )
            
            # 生成优化建议
            optimization_results['recommendations'] = self._generate_optimization_recommendations(
                access_analysis
            )
            
            # 计算性能改进
            if self.stats.total_requests > 0:
                optimization_results['performance_improvement'] = (
                    self.stats.hit_rate - 0.5  # 假设基准命中率为50%
                )
            
            return optimization_results
            
        except Exception as e:
            print(f"缓存策略优化失败: {e}")
            return optimization_results
    
    def _analyze_access_patterns(self) -> Dict[str, Any]:
        """分析访问模式"""
        analysis = {
            'total_unique_keys': len(self.key_access_patterns),
            'hot_keys_count': len([k for k, v in self.hot_keys.items() if v > 5]),
            'cold_keys_count': len([k for k, v in self.hot_keys.items() if v <= 2]),
            'access_frequency_distribution': {},
            'temporal_patterns': {}
        }
        
        # 访问频率分布
        frequency_ranges = [(1, 2), (3, 5), (6, 10), (11, 50), (51, float('inf'))]
        for low, high in frequency_ranges:
            count = len([k for k, v in self.hot_keys.items() if low <= v <= high])
            range_name = f"{low}-{high if high != float('inf') else '∞'}"
            analysis['access_frequency_distribution'][range_name] = count
        
        # 时间模式分析
        current_time = datetime.now()
        for key, access_times in self.key_access_patterns.items():
            if access_times:
                recent_accesses = [
                    t for t in access_times 
                    if (current_time - t).total_seconds() < 3600  # 最近1小时
                ]
                if recent_accesses:
                    analysis['temporal_patterns'][key] = len(recent_accesses)
        
        return analysis
    
    async def _optimize_ttl_settings(self, access_analysis: Dict[str, Any]) -> List[str]:
        """优化TTL设置"""
        optimizations = []
        
        # 为热点键延长TTL
        hot_keys = [k for k, v in self.hot_keys.items() if v > 10]
        for key in hot_keys[:10]:  # 限制前10个热点键
            current_ttl = self._get_adaptive_ttl(key)
            new_ttl = int(current_ttl * 1.5)
            
            # 更新键模式配置
            for pattern in self.key_patterns:
                import fnmatch
                if fnmatch.fnmatch(key, pattern):
                    self.key_patterns[pattern] = new_ttl
                    optimizations.append(f"延长热点键 {pattern} 的TTL到 {new_ttl}秒")
                    break
        
        return optimizations
    
    def _optimize_hot_keys(self) -> List[str]:
        """优化热点键"""
        optimizations = []
        
        # 识别需要预加载的热点键
        hot_keys = sorted(self.hot_keys.items(), key=lambda x: x[1], reverse=True)[:5]
        
        for key, access_count in hot_keys:
            if access_count > 20:
                optimizations.append(f"标记 {key} 为预加载候选（访问次数: {access_count}）")
        
        return optimizations
    
    async def _cleanup_cold_keys(self) -> int:
        """清理冷键"""
        cleaned_count = 0
        current_time = datetime.now()
        
        # 识别冷键（超过1小时未访问且访问次数少于3次）
        cold_keys = []
        for key, access_times in self.key_access_patterns.items():
            if access_times:
                last_access = max(access_times)
                if ((current_time - last_access).total_seconds() > 3600 and 
                    self.hot_keys.get(key, 0) < 3):
                    cold_keys.append(key)
        
        # 删除冷键
        for key in cold_keys[:50]:  # 限制一次清理的数量
            if await self.delete(key):
                cleaned_count += 1
                # 清理访问记录
                if key in self.hot_keys:
                    del self.hot_keys[key]
                if key in self.key_access_patterns:
                    del self.key_access_patterns[key]
        
        return cleaned_count
    
    def _generate_optimization_recommendations(self, access_analysis: Dict[str, Any]) -> List[str]:
        """生成优化建议"""
        recommendations = []
        
        # 基于命中率的建议
        if self.stats.hit_rate < 0.6:
            recommendations.append("缓存命中率较低，建议增加缓存容量或优化缓存策略")
        
        # 基于访问模式的建议
        hot_keys_count = access_analysis.get('hot_keys_count', 0)
        if hot_keys_count > 20:
            recommendations.append("热点键较多，建议实施缓存预热策略")
        
        cold_keys_count = access_analysis.get('cold_keys_count', 0)
        if cold_keys_count > 100:
            recommendations.append("冷键过多，建议定期清理或缩短TTL")
        
        # 基于响应时间的建议
        if self.stats.average_response_time_ms > 10:
            recommendations.append("缓存响应时间较长，建议检查网络连接或优化序列化")
        
        return recommendations
    
    async def close(self):
        """关闭缓存服务"""
        try:
            if self.redis_client:
                await self.redis_client.close()
                print("Redis连接已关闭")
        except Exception as e:
            print(f"关闭缓存服务失败: {e}")


# 缓存装饰器
def cached(expire_seconds: Optional[int] = None, key_prefix: str = ""):
    """缓存装饰器"""
    def decorator(func):
        async def wrapper(*args, **kwargs):
            # 生成缓存键
            cache_key = f"{key_prefix}:{func.__name__}:{_generate_cache_key(args, kwargs)}"
            
            # 尝试从缓存获取
            if hasattr(wrapper, '_cache_service'):
                cached_result = await wrapper._cache_service.get(cache_key)
                if cached_result is not None:
                    return cached_result
            
            # 执行函数
            result = await func(*args, **kwargs)
            
            # 存储到缓存
            if hasattr(wrapper, '_cache_service') and result is not None:
                await wrapper._cache_service.set(cache_key, result, expire_seconds)
            
            return result
        
        return wrapper
    return decorator


def _generate_cache_key(args: tuple, kwargs: dict) -> str:
    """生成缓存键"""
    key_data = {
        'args': args,
        'kwargs': sorted(kwargs.items())
    }
    key_string = json.dumps(key_data, sort_keys=True, default=str)
    return hashlib.md5(key_string.encode()).hexdigest()[:16]